#include <cstdlib>
#include <cmath>
#include <iostream>
#include <allegro.h>

#define PI (atan(1)*4)
#define COUNT_OF(x) ( sizeof(x) / sizeof((x)[0]) )

using namespace std;

/*
 * Definição de tipos de dados
 */

struct t_point {
	float x, y;
};

struct t_vertex {
	float x, y, z;
};

struct t_size {
	float w, h;
};

struct t_config {
	int color_depth;
	int screen_mode; // um dos modos GFX_* do allegro
	int screen_w, screen_h;
	int sync_time; // tempo de sincronia em milisegundos
};

struct t_model {
	struct t_vertex pivot;
	struct t_size size;
	BITMAP* texture;
	PALETTE palette;
};

// o pivot zero do objeto (x=0,y=0,z=0) representa o centro da base do sprite
struct t_object {
	struct t_cell *cell;
	struct t_model *model;
};

// objetos componentes de uma casa
struct t_cell {
	struct t_vertex position;
	float elevation;
	struct t_object ground, back_stuff, front_stuff, character;
	t_cell() {
		ground.cell = back_stuff.cell = front_stuff.cell = character.cell = this;
	}
};

struct t_map {
	// tamanho do mapa nos eixos x e z
	int size_x, size_z;

	// será alocado a matriz para x,z contendo o objetos de cada casa
	struct t_cell **cells;

};

/*
 * Definição de métodos
 */

// estrutural
void init() throw(char*);
void load() throw(char*);
void prepare() throw(char*);
void run() throw(char*);
void endings() throw(char*);
void unload() throw(char*);

// pré-cálculo do ponto de vista do jogador
void make_point_of_view();

// processos
void input();
void paint();
void process();

// subprocessos
void paint(struct t_model& model, struct t_vertex& position);

/*
 * Definição de dados
 */

// eixos do espaço tridimensional
struct t_point x_axis, y_axis, z_axis;

// configuração
struct t_config cfg;

// ponto de vista
float rotation = PI * 0.25;
float elevation = 1.1;
float zoom = 1.0;

BITMAP *buffer;

struct t_model models[10];

// mapa
struct t_map map;

float pixels_per_point = 50;

/*
 * Programa construído
 */

int main() {
	try {
		init();
		load();
		prepare();
		run();
		endings();
		unload();
	} catch (char* e) {
		cerr << "main:SEVERE:" << e << endl;
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}
END_OF_MAIN();

void init() throw(char*) {

  cfg.color_depth = 32;
  cfg.screen_mode = GFX_AUTODETECT_WINDOWED;
  cfg.screen_w = 800;
  cfg.screen_h = 600;
  cfg.sync_time = 10;

  if (allegro_init() != 0)
  	throw "Cannot initialize allegro.";
  install_timer();
  install_keyboard();

  set_color_depth(cfg.color_depth);

  if (set_gfx_mode(cfg.screen_mode, cfg.screen_w, cfg.screen_h, 0, 0) != 0)
          throw "Unable to set up the graphic mode.";

  buffer = create_bitmap(SCREEN_W, SCREEN_H);
  set_palette(desktop_palette);
}

void load() throw(char*) {
	struct t_cell *cell;

	map.size_x = map.size_z = 10;
	map.cells = new struct t_cell*[map.size_x];
	for (int x = 0; x < map.size_x; x++) {
		map.cells[x] = new struct t_cell[map.size_z];
		for (int z = 0; z < map.size_z; z++) {
			map.cells[x][z].position.x = x;
      map.cells[x][z].position.z = z;
		}
	}

	// modelos (size é dado em pixels, pivot é dado em pontos)
	models[0].size.w = 75;
	models[0].size.h = 34;
	models[0].pivot.y = (models[0].size.h * 0.5) / pixels_per_point;
	models[0].texture = load_bitmap("ground.bmp", models[0].palette);

  models[1].size.w = 40;
  models[1].size.h = 90;
  models[1].pivot.y = 5 / pixels_per_point;
  models[1].texture = load_bitmap("woman.bmp", models[1].palette);

	// chão
	for (int x = 0; x < map.size_x; x++) {
		for (int z = 0; z < map.size_z; z++) {
			cell = &map.cells[x][z];
			cell->ground.model = &models[0];
		}
	}

	// personagem
	map.cells[5][5].character.model = &models[1];
}

void prepare() throw(char*) {
}

void run() throw(char*) {
	while (!key[KEY_ESC]) {
		clear_bitmap(buffer);

		input();

		process();

		make_point_of_view();
		paint();

		blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
		rest(cfg.sync_time);
	}
}
void endings() throw(char*) {
	destroy_bitmap(buffer);
}
void unload() throw(char*) {
	for (int i = 0; i < map.size_z; i++) {
		delete[] map.cells[i];
		map.cells[i] = NULL;
	}
	delete[] map.cells;

	for (int i = 0; i < COUNT_OF(models); i++) {
		destroy_bitmap(models[i].texture);
	}
}

void make_point_of_view() {
	x_axis.x = cos(rotation) * zoom;
	x_axis.y = sin(rotation) * cos(elevation) * zoom;
	z_axis.x = -sin(rotation) * zoom;
	z_axis.y = cos(rotation) * cos(elevation) * zoom;
	y_axis.x = 0.0 * zoom;
	y_axis.y = sin(elevation) * zoom;
}

void input() {
	if (key[KEY_EQUALS] || key[KEY_PLUS_PAD])
		zoom *= 1.01;
	if (key[KEY_MINUS] || key[KEY_MINUS_PAD])
		zoom *= 0.99;
}

void paint() {
  struct t_cell *cell;
	for (int x = 0; x < map.size_x; x++) {
		for (int z = 0; z < map.size_z; z++) {
			cell = &map.cells[x][z];

			if (cell->ground.model != NULL) {
				paint(*cell->ground.model, cell->position);
			}

      if (cell->character.model != NULL) {
      	paint(*cell->character.model, cell->position);
      }
		}
	}
}

void paint(struct t_model& model, struct t_vertex& position) {
  float x, y, z, w, h, screen_x, screen_y;

  w = model.size.w * zoom;
  h = model.size.h * zoom;

  x = position.x + model.pivot.x;
  y = position.y + model.pivot.y;
  z = position.z + model.pivot.z;

  screen_x = x * x_axis.x + y * y_axis.x + z * z_axis.x;
  screen_y = x * x_axis.y + y * y_axis.y + z * z_axis.y;

  // ponto para pixel
  screen_x *= pixels_per_point;
  screen_y *= pixels_per_point;

  // relacionado a cooredenada com o centro da tela
  screen_x += SCREEN_W * 0.5;
  screen_y += SCREEN_H * 0.5;

  // o pivot zero refere-se ao centro da base do sprite
  screen_x -= (w * 0.5);
  screen_y -= h;

  set_palette(model.palette);
  stretch_sprite(buffer, model.texture,
                  (int) floor(screen_x),
                  (int) floor(screen_y),
                  (int) floor(w),
                  (int) floor(h));
}

void process() {
}

/*
 BITMAP *image;

 float default_sprite_width = 1.5;
 float default_sprite_height = 3.0;

 float x, y, z, screen_x, screen_y;

 float sprite_x = 3;
 float sprite_y = 3;
 float sprite_width, sprite_height;

 int i;



 void make_poly_dot(int *poly, int index, float x, float y)
 {
 z = (sin(x/2) + cos(y/2)) * 2;
 screen_x = x * x_axis.x + y * z_axis.x + z * y_axis.x;
 screen_y = x * x_axis.y + y * z_axis.y + z * y_axis.y;
 screen_x += SCREEN_W * 0.5;
 screen_y += SCREEN_H * 0.5;
 poly[index] = (int)floor(screen_x);
 poly[index+1] = (int)floor(screen_y);
 }

 void make_poly(int *poly, float x, float y)
 {
 make_poly_dot(poly, 0, x-0.5, y-0.5);
 make_poly_dot(poly, 2, x-0.5, y+0.5);
 make_poly_dot(poly, 4, x+0.5, y+0.5);
 make_poly_dot(poly, 6, x+0.5, y-0.5);
 make_poly_dot(poly, 8, x-0.5, y-0.5);
 }

 int _main(void)
 {

 image = load_bitmap("/home/fernando/work/c/motora/branches/rascunho/movimento/woman.bmp", palette);


 while (!key[KEY_ESC])
 {
 clear_bitmap(buffer);

 if (key[KEY_4_PAD]) rotation += 0.01;
 if (key[KEY_6_PAD]) rotation -= 0.01;
 if (key[KEY_8_PAD]) elevation += 0.01;
 if (key[KEY_2_PAD]) elevation -= 0.01;
 if (key[KEY_EQUALS] || key[KEY_PLUS_PAD]) zoom *= 1.01;
 if (key[KEY_MINUS] || key[KEY_MINUS_PAD]) zoom *= 0.99;

 if (key[KEY_UP] && key[KEY_LEFT]) {
 sprite_x -= 1;
 }
 if (key[KEY_UP] && key[KEY_RIGHT]) {
 sprite_x += 1;
 }
 if (key[KEY_DOWN] && key[KEY_LEFT]) {
 sprite_y += 1;
 }
 if (key[KEY_DOWN] && key[KEY_LEFT]) {
 sprite_y -= 1;
 }

 if (key[KEY_UP]) {
 sprite_x -= 1;
 sprite_y -= 1;
 }
 if (key[KEY_DOWN]) {
 sprite_x += 1;
 sprite_y += 1;
 }
 if (key[KEY_LEFT]) {
 sprite_x -= 1;
 sprite_y += 1;
 }
 if (key[KEY_RIGHT]) {
 sprite_x += 1;
 sprite_y -= 1;
 }

 if (sprite_x < -15.0) sprite_x = -15.0;
 if (sprite_x >  15.0) sprite_x =  15.0;
 if (sprite_y < -15.0) sprite_y = -15.0;
 if (sprite_y >  15.0) sprite_y =  15.0;

 // precalculated vectors
 x_axis.x = cos(rotation) * zoom;
 x_axis.y = sin(rotation) * cos(elevation) * zoom;
 z_axis.x = -sin(rotation) * zoom;
 z_axis.y = cos(rotation) * cos(elevation) * zoom;
 y_axis.x = 0.0 * zoom;
 y_axis.y = sin(elevation) * zoom;

 sprite_width = default_sprite_width * zoom;
 sprite_height = default_sprite_height * zoom;

 // painting the ground
 for (x=-15.0; x<=15.0; x+=1.0)
 {
 for (y=-15.0; y<=15.0; y+=1.0)
 {

 // drawing the ground
 int poly[10];
 make_poly(poly, x, y);

 polygon(buffer, 5, poly, makecol(0,200,0));

 for (i = 2; i < 10; i+=2)
 {
 line(buffer,
 poly[i-2],
 poly[i-1],
 poly[i],
 poly[i+1],
 makecol(0,100,0));
 }

 if (sprite_x == x && sprite_y == y)
 {
 z = (sin(x/2) + cos(y/2)) * 2;

 z += 0.2;

 screen_x = x * x_axis.x + y * z_axis.x + z * y_axis.x;
 screen_y = x * x_axis.y + y * z_axis.y + z * y_axis.y;

 screen_x -= (sprite_width * 0.5);
 screen_y -= sprite_height;
 BITMAP *stretched;
 stretch_sprite(buffer, image,
 (int)floor(screen_x + SCREEN_W * 0.5),
 (int)floor(screen_y + SCREEN_H * 0.5),
 sprite_width, sprite_height);
 }
 }
 }



 textprintf_ex(buffer, font, 10, 10, makecol(255, 255, 255), -1,
 "rotation.: % 06.2f", rotation);
 textprintf_ex(buffer, font, 10, 20, makecol(255, 255, 255), -1,
 "elevation: % 06.2f", elevation);

 blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
 rest(20);
 }

 return 0;
 }
 */

